#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include "mypipe.h"

// pipe queue
struct mypipe_st
{
  int head; // begin
  int tail; // end
  char data[PIPESIZE];
  int datasize;
  int cnt_rd;
  int cnt_wr;
  pthread_mutex_t mut;
  pthread_cond_t cond;
};

mypipe_t *mypipe_init(void)
{
  struct mypipe_st *me = (struct mypipe_st *)malloc(sizeof(*me));
  if (me == NULL)
    return NULL;
  me->head = 0;
  me->tail = 0;
  me->datasize = 0;
  me->cnt_rd = 0;
  me->cnt_wr = 0;
  pthread_mutex_init(&me->mut, NULL);
  pthread_cond_init(&me->cond, NULL);

  return me;
}

int mypipe_register(mypipe_t *ptr, int opmap)
{
  struct mypipe_st *me = (struct mypipe_st *)ptr;
  pthread_mutex_lock(&me->mut);
  if (opmap & MYPIPE_READ)
    me->cnt_rd++;
  if (opmap & MYPIPE_WRITE)
    me->cnt_wr++;

  while (me->cnt_rd <= 0 || me->cnt_wr <= 0)
    pthread_cond_wait(&me->cond, &me->mut);
  pthread_cond_broadcast(&me->cond);
  pthread_mutex_unlock(&me->mut);

  return 0;
}

int mypipe_unregister(mypipe_t *ptr, int opmap)
{
  struct mypipe_st *me = (struct mypipe_st *)ptr;

  pthread_mutex_lock(&me->mut);
  if (opmap & MYPIPE_READ)
    me->cnt_rd--;
  if (opmap & MYPIPE_WRITE)
    me->cnt_wr--;
  pthread_cond_broadcast(&me->cond);
  pthread_mutex_unlock(&me->mut);

  return 0;
}


static int mypipe_readbyte_unlocked(struct mypipe_st ptr, char *buf)
{
  if (me->datasize <= 0)
    return -1;
  *buf = me->data[me->head];
  me->head = next(me->head);
  me->datasize--;
  return 0;
}

static int mypipe_writebyte_unlocked(struct mypipe_st *ptr, char *buf)
{
  if(me->datasize == PIPESIZE)
    return -1;
  me->data[me->tail] = *buf;
  me->tail = next(me->tail);
  me->datasize++;
  return 0;
}

int mypipe_read(mypipe_t *ptr, void *buf, size_t size)
{
  int i;
  struct mypipe_st *me = (struct mypipe_st *)ptr;
  pthread_mutex_lock(&me->mut);

  while (me->datasize <= 0 && me->cnt_wr > 0)
    pthread_cond_wait(&me->cond, &me->mut);

  if (me->datasize <= 0 && me->cnt_wr <= 0)
  {
    pthread_mutex_unlock(&me->mut);
    return 0;
  }
  
  for(i = 0; i < size; i++)
  {
    if(mypipe_readbyte_unlocked(me, buf + i) < 0)
      break;
  }
  pthread_cond_broadcast(&me->cond);
  pthread_mutex_unlock(&me->mut);

  return i;
}

int mypipe_write(mypipe_t *ptr, const void *buf, size_t size)
{
  int i;
  struct mypipe_st *me = (struct mypipe_st *)ptr;
  pthread_mutex_lock(&me->mut);

  while (me->datasize == PIPESIZE && me->cnt_rd > 0)
    pthread_cond_wait(&me->cond, &me->mut);

  if (me->datasize == PIPESIZE && me->cnt_rd <= 0)
  {
    pthread_mutex_unlock(&me->mut);
    return 0;
  }

  for(i = 0; i < size; i++)
  {
    if(mypipe_writebyte_unlocked(me, buf + i) < 0)
      break;
  }
  pthread_cond_broadcast(&me->cond);
  pthread_mutex_unlock(&me->mut);

  return i;
}

int mypipe_destroy(mypipe_t *ptr)
{
  struct mypipe_st *me = (struct mypipe_st *)ptr;
  pthread_mutex_destroy(&me->mut);
  pthread_cond_destroy(&me->cond);
  free(me);
  return 0;
}

